﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BusinessObjects;
using System.Data.Services.Client;
using System.Xml.Serialization;
using System.IO;
using TcpServer.GreenWorldEntitiesService;
using TcpLib;

namespace YourWorldServer
{
    public class GameManager
    {
        private GreenWorldEntities entities;

        private static GameManager instance;

        private GameManager( string url )
        {
            this.Games = new Dictionary<int, Game>();
            this.Players = new Dictionary<int, PlayerStream>();

            this.entities = new GreenWorldEntities( new Uri( url, UriKind.Absolute ) );
            RegionObjectsRepository.Instance.Initialize();
            RubbishBinsRepository.Instance.Initialize();
        }

        /// <summary>
        /// player Id -> PlayerStream (used for sendeing a message to the player)
        /// </summary>
        public Dictionary<int, PlayerStream> Players
        {
            get;
            private set;
        }

        public Dictionary<int, Game> Games
        {
            get;
            private set;
        }

        public Action<Game> GameInitializationCompleted
        {
            get;
            set;
        }

        public static string ServiceUrl
        {
            get;
            set;
        }

        public static GameManager Instance
        {
            get
            {
                if ( instance == null )
                    instance = new GameManager( ServiceUrl );
                return instance;
            }
        }

        public void InitializeNewGame( int mapId )
        {
            int mapToSearchId = mapId;

            //Workaround for singleplayer
            if ( mapId != 1 ) // The Map's Id in the DB
                mapToSearchId = 1;

            var regions = ( from r in entities.Regions where r.MapId == mapToSearchId select r ) as DataServiceQuery<Region>;
            regions.BeginExecute( new AsyncCallback( this.RegionsLoaded ), new GameInit()
            {
                MapId = mapId,
                RegionsQuery = regions
            } );
        }

        public void AddGamePlayer( PlayerStream player )
        {
            if ( this.Players.ContainsKey( player.Player.Id ) == false )
                this.Players.Add( player.Player.Id, player );
            else
                this.Players[player.Player.Id] = player;
        }

        public void RemoveGamePlayer( PlayerStream player )
        {
            if ( this.Players.ContainsKey( player.Player.Id ) == true )
                this.Players.Remove( player.Player.Id );
        }

        public void AddQuestToMap( int x, int y, int questId, Game playerGame )
        {
            RegionObject quest = RegionObjectsRepository.Instance.GetRegionObject( questId );
            BuildQuestTypes questType = ( BuildQuestTypes )Enum.Parse( typeof( BuildQuestTypes ), quest.Template, true );

            int gridx = playerGame.Grid.Count - ( int )( x / Consts.MapTileWidth ) - 1;
            int gridy = ( int )( y / Consts.MapTileHeight );

            if ( gridx < 0 || gridy < 0 || playerGame.Grid[gridx][gridy] == Consts.Unreachable )
                return;

            playerGame.Grid[gridx][gridy] = questId;
            playerGame.SimplifiedGameObjects.Add( new RegionObjectHolder( questId, gridx, gridy ) );            
        }

        public ConnectionState GetPlayerCommunicationChannel( int playerId )
        {
            if ( this.Players.ContainsKey( playerId ) == false )
                return null;

            return this.Players[playerId].Stream;
        }

        public Player GetPlayer( int playerId )
        {
            if ( this.Players.ContainsKey( playerId ) == false )
                return null;

            return this.Players[playerId].Player;
        }

        private List<GameObject> GetRegionObject( Region region )
        {
            return null;
        }

        public Game GetGame( int mapId )
        {
            if ( this.Games.ContainsKey( mapId ) == true )
                return this.Games[mapId];

            this.InitializeNewGame( mapId );
            return null;
        }

        public List<RegionObjectHolder> GenerateRubbish( Game game, int rubbishCount )
        {
            return this.LoadMapRubbishesLocations( game, rubbishCount );
        }

        private void RegionsLoaded( IAsyncResult state )
        {
            GameInit gameInit = state.AsyncState as GameInit;

            Game game = new Game();
            game.MapId = gameInit.MapId;

            this.Games.Add( gameInit.MapId, game );
            this.LoadMapGrid( game.MapId );
        }

        private List<RegionObjectHolder> LoadMapRubbishesLocations( Game game, int rubbishCount )
        {
            List<RegionObjectHolder> result = new List<RegionObjectHolder>();
            int x, y, id;
            int count = RegionObjectsRepository.Instance.RubbishIds.Count;

            Random rand = new Random();
            RegionObjectHolder roh;

            while ( rubbishCount > 0 )
            {
                x = rand.Next( 0, game.Grid.Count );
                y = rand.Next( 0, game.Grid[x].Count );

                id = RegionObjectsRepository.Instance.RubbishIds[rand.Next( 0, count - 1 )];

                if ( game.Grid[x][y] == 0 )
                {
                    game.Grid[x][y] = id;
                    roh = new RegionObjectHolder( id, x, y );

                    game.SimplifiedGameObjects.Add( roh );
                    result.Add( roh );

                    rubbishCount--;
                }
            }

            return result;
        }

        private void LoadMapGrid( int mapId )
        {
            //Debug.Assert( this.Games.ContainsKey( mapId ) );

            int mapToSearchId = mapId;

            //Workaround for singleplayer
            if ( mapId != 1 ) // The Map's Id in the DB
                mapToSearchId = 1;

            var map = ( from r in entities.Maps where r.Id == mapToSearchId select r ) as DataServiceQuery<Map>;
            map.BeginExecute( new AsyncCallback( this.MapLoaded ), new GameInit()
            {
                MapId = mapId,
                MapQuery = map
            } );
        }

        private void MapLoaded( IAsyncResult state )
        {
            GameInit gameInit = state.AsyncState as GameInit;
            List<Map> mapList = gameInit.MapQuery.EndExecute( state ).ToList();

            //Debug.Assert( mapList.Count == 1, "Map with Id " + gameInit.MapId + " is not found! " );

            Map map = mapList[0];
            Game game = this.Games[gameInit.MapId];

            XmlSerializer xmlSrl = new XmlSerializer( typeof( List<string> ) );
            StringReader stream = new StringReader( map.GridDefinition );

            game.GridDefinition = xmlSrl.Deserialize( stream ) as List<string>;

            List<int> items;
            string[] itemsString;

            foreach ( string row in game.GridDefinition )
            {
                itemsString = row.Split( new char[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries );
                items = new List<int>();

                foreach ( string number in itemsString )
                    items.Add( Int16.Parse( number ) );

                game.Grid.Add( items );
            }

            game.SimplifiedGameObjects = new List<RegionObjectHolder>();
            this.LoadMapRubbishesLocations( game, Consts.UsualRubbishCount );

            if ( this.GameInitializationCompleted != null )
                this.GameInitializationCompleted( game );
        }
    }

    class GameInit
    {
        public int MapId
        {
            get;
            set;
        }

        public DataServiceQuery<Region> RegionsQuery
        {
            get;
            set;
        }

        public DataServiceQuery<Map> MapQuery
        {
            get;
            set;
        }
    }

}
